/* Sentinel - IRC Statistical and Operator Services
** challenge.c - Functions to handle .challenge, OpenSSL replacement to
**               .login.
**
** Copyright W. Campbell and others.  See README for more details
** Some code Copyright: Jonathan George, Kai Seidler, ircd-hybrid Team,
**                      IRCnet IRCD developers.
**
** $Id: challenge.c 7 2010-02-01 19:17:42Z dubkat $
*/

#include "stats.h"
#include "struct.h"
#include "proto.h"
#include "slog.h"
#include "mem.h"

#ifdef HAVE_LIBCRYPTO

/* Most of this was taken right out of ircd-ratbox-1 (or ircd-hybrid-7,
** since the challenge code is the same)
*/

static int get_randomness(unsigned char *, int);
static void report_crypto_errors(void);
static void binary_to_hex(unsigned char *, char *, int);

static void binary_to_hex(unsigned char *bin, char *hex, int length)
{
  const char *trans = "0123456789ABCDEF";
  int i;

  for (i = 0; i < length; i++)
  {
    hex[i << 1] = trans[bin[i] >> 4];
    hex[(i << 1) + 1] = trans[bin[i] & 0xf];
  }
  hex[i << 1] = '\0';
}

static int get_randomness(unsigned char *buf, int length)
{
  if (RAND_status())
    return RAND_bytes(buf, length);
  else                    /* XXX - abort? */
    return RAND_pseudo_bytes(buf, length);
}

static void report_crypto_errors(void)
{
  unsigned long e = 0;
  unsigned long cnt = 0;

  ERR_load_crypto_strings();
  while ((cnt < 100) && (e = ERR_get_error()))
  {
    slog(DEFAULT_LOG, L_ERR, "SSL error: %s", ERR_error_string(e, 0));
    cnt++;
  }
}

int generate_challenge(char **r_challenge, char **r_response, RSA *rsa)
{
  unsigned char secret[32], *tmp;
  unsigned long length, ret;

  if (!rsa)
    return -1;
  get_randomness(secret, 32);
  *r_response = smalloc(65);
  binary_to_hex(secret, *r_response, 32);

  length = RSA_size(rsa);
  tmp = smalloc(length);
  ret = RSA_public_encrypt(32, secret, tmp, rsa, RSA_PKCS1_PADDING);

  *r_challenge = smalloc((length << 1) + 1);
  binary_to_hex(tmp, *r_challenge, length);
  (*r_challenge)[length << 1] = 0;
  free(tmp);

  if (ret < 0)
  {
    report_crypto_errors();
    return ( -1);
  }
  return (0);
}

#endif
